﻿//Helper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.Algo.Testing;
	using StockSharp.BusinessEntities;

	static class Helper
	{
		private sealed class MarketStopWatch
		{
			private readonly Order _order;
			private readonly Stopwatch _watch;
			private DateTime _start;
			private DateTime _end;

			public MarketStopWatch(Order order)
			{
				if (order == null)
					throw new ArgumentNullException("order");

				_order = order;
				_watch = order.Trader is IEmulationTrader ? null : new Stopwatch();
				Start();
			}

			private void Start()
			{
				if (null != _watch)
					_watch.Start();

				_start = _order.Security.GetMarketTime();
			}

			public TimeSpan Stop()
			{
				if (null != _watch)
					_watch.Stop();

				_end = _order.Security.GetMarketTime();
				return _watch != null ? _watch.Elapsed : _end - _start;
			}
		}

		public static void ChechOrderState(this Order order, bool checkVolume = true)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (order.Type == OrderTypes.Conditional && order.Condition == null)
				throw new ArgumentException("Условная заявка не имеет условие.", "order");

			if (order.Security == null)
				throw new ArgumentException("Отсутствует информация по инструменту.", "order");

			if (order.Portfolio == null)
				throw new ArgumentException("Портфель не был найден.", "order");

			if (order.Price < 0)
				throw new ArgumentOutOfRangeException("order", order.Price, "Цена заявки должна быть положительной.");

			if (order.Price == 0 && (order.Type == OrderTypes.Limit || order.Type == OrderTypes.ExtRepo || order.Type == OrderTypes.Repo || order.Type == OrderTypes.Rps))
				throw new ArgumentException("Цена лимитной заявки не может быть равной 0.", "order");

			if (checkVolume && order.Volume == 0)
				throw new ArgumentException("Объем заявки не может быть нулевым.", "order");

			if (checkVolume && order.Volume < 0)
				throw new ArgumentOutOfRangeException("order", order.Volume, "Объем заявки не может быть отрицательным.");
		}

		public static void CheckOnNew(this Order order, bool checkVolume = true)
		{
			order.ChechOrderState(checkVolume);

			if (order.Id != 0)
				throw new ArgumentException("Заявка уже имеет номер '{0}'. Возможно, она уже была зарегистрирована.".Put(order.Id), "order");

			if (order.TransactionId != 0 && !order.IsTrace)
				throw new ArgumentException("Заявка уже имеет номер транзакции '{0}'. Возможно, она уже была зарегистрирована.".Put(order.TransactionId), "order");

			if (order.State != OrderStates.None)
				throw new ArgumentException("Заявка уже имеет состояние '{0}'. Возможно, она уже была зарегистрирована.".Put(order.State), "order");
		}

		private static readonly SynchronizedDictionary<Order, MarketStopWatch> _latencyRegistration = new SynchronizedDictionary<Order, MarketStopWatch>();
		private static readonly SynchronizedDictionary<Order, MarketStopWatch> _latencyCancellation = new SynchronizedDictionary<Order, MarketStopWatch>();

		public static void InitOrder(this Order order, ITrader trader, TransactionIdGenerator transactionIdGenerator)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (trader == null)
				throw new ArgumentNullException("trader");

			if (transactionIdGenerator == null)
				throw new ArgumentNullException("transactionIdGenerator");

			order.Balance = order.Volume;

			if (order.ExtensionInfo == null)
				order.ExtensionInfo = new Dictionary<object, object>();

			//order.InitializationTime = trader.MarketTime;
			if (order.TransactionId == 0)
				order.TransactionId = transactionIdGenerator.GetNextId();
			
			order.Trader = trader;

			if (order.Security is ContinuousSecurity)
				order.Security = ((ContinuousSecurity)order.Security).GetSecurity();

			order.InitLatencyMonitoring(true);
		}

		public static void InitLatencyMonitoring(this Order order, bool isRegistration)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			var watch = new MarketStopWatch(order);

			var dict = isRegistration ? _latencyRegistration : _latencyCancellation;

			lock (dict.SyncRoot)
			{
				if (dict.ContainsKey(order))
					throw new ArgumentException((isRegistration ? "Заявка {0} была ранее отправлена на регистрацию. Перерегистрация заявки осуществляется через метод ReRegisterOrder." : "Заявка {0} была ранее отправлена на отмену.").Put(order.TransactionId), "order");

				dict.Add(order, watch);
			}
		}

		public static TimeSpan GetLatency(this Order order, bool isRegistration)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			return (isRegistration ? _latencyRegistration : _latencyCancellation).SyncGet(d =>
			{
				var latency = TimeSpan.Zero;

				var watch = d.TryGetValue(order);

				if (watch != null)
				{
					latency = watch.Stop();
					d.Remove(order);
				}

				return latency;
			});
		}

		public static void FillLatencyCancellation(this IEnumerable<Order> orders)
		{
			if (orders == null)
				throw new ArgumentNullException("orders");

			foreach (var order in orders)
			{
				if (order.IsCanceled())
					order.LatencyCancellation = order.GetLatency(false);
			}
		}

		public static void DeleteLatencyMonitoring(this IEnumerable<OrderFail> fails, bool isRegistration)
		{
			if (fails == null)
				throw new ArgumentNullException("fails");

			var dict = isRegistration ? _latencyRegistration : _latencyCancellation;

			foreach (var fail in fails)
				dict.Remove(fail.Order);
		}

		public static void CheckOnOld(this Order order)
		{
			order.ChechOrderState(false);

			if (order.TransactionId == 0 && order.Id == 0)
				throw new ArgumentException("Заявка не имеет ни идентификатор ни номер транзакции. Возможно, она не была зарегистрирована.", "order");
		}

		public static void CheckOption(this Security option)
		{
			option.CheckTrader();

			if (option.Type != SecurityTypes.Option)
				throw new ArgumentException("Неправильный тип {0} инструмента.".Put(option.Type), "option");

			if (option.OptionType == null)
				throw new ArgumentException("Тип опциона {0} отсутствует.".Put(option), "option");

			if (option.ExpiryDate == null)
				throw new ArgumentException("Дата экспирации опциона {0} отсутствует.".Put(option), "option");

			if (option.UnderlyingSecurityId == null)
				throw new ArgumentException("Информация о базовом инструменте {0} отсутствует.".Put(option), "option");
		}

		public static ITrader CheckTrader(this Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (security.Trader == null)
				throw new ArgumentException("Инструмент {0} не имеет информацию о шлюзе.".Put(security.Id), "security");

			return security.Trader;
		}

		public static ExchangeBoard CheckExchangeBoard(this Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (security.ExchangeBoard == null)
				throw new ArgumentException("У инструмента {0} отсутствует информация о биржевой площадке.".Put(security), "security");

			return security.ExchangeBoard;
		}

		public static ITrader CheckTrader(this Order order)
		{
			if (order == null)
				throw new ArgumentNullException("order");

			if (order.Trader == null)
				throw new ArgumentException("Заявка {0} не имеет информацию о шлюзе.".Put(order.TransactionId), "order");

			return order.Trader;
		}

		public static bool ChangeContinuousSecurity(this ITrader trader, Order order)
		{
			if (trader == null)
				throw new ArgumentNullException("trader");

			if (order == null)
				throw new ArgumentNullException("order");

			var cs = order.Security as ContinuousSecurity;

			while (cs != null)
			{
				order.Security = cs.GetSecurity();
				cs = order.Security as ContinuousSecurity;
			}

			return true;
		}

		public static Security CheckMinStepSize(this Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			if (security.MinStepSize == 0)
				throw new ArgumentException("Инструмент {0} имеет нулевой шаг цены.".Put(security.Id));

			return security;
		}
	}
}